package com.solome.util;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;

import cn.hutool.core.io.FileUtil;
import cn.hutool.http.HttpException;
import cn.hutool.setting.dialect.Props;
import cn.hutool.setting.dialect.PropsUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.*;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * solome/ 优化的 HTTP 客户端，基于 Java 11+ HttpClient，支持动态代理配置、同步/异步请求及 JSON 解析。
 * 提供单例模式、自动重试机制和灵活的代理管理。
 */
public class AsHttpClient {
	private static final String DATA_DIR = System.getProperty("user.dir"); // 当前工作目录
	private static final Logger logger = LoggerFactory.getLogger(AsHttpClient.class);
	private static final String DEFAULT_ENCODING = StandardCharsets.UTF_8.name(); // 默认字符编码
	private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(10); // 默认超时时间
	private static final int DEFAULT_MAX_RETRIES = 3; // 默认最大重试次数

	private HttpClient httpClient; // HTTP 客户端实例
//	private final AtomicReference<ProxySelector> proxySelectorRef; // 动态代理选择器
//	private final AtomicReference<Authenticator> authenticatorRef; // 动态认证器
	private final boolean debug; // 调试模式
	private final int maxRetries; // 最大重试次数

	// 配置属性
	private static final Props config = loadConfig();

	/**
	 * 获取单例实例，使用初始化时延迟加载。
	 *
	 * @return AsHttpClient 实例
	 */
	public static AsHttpClient getInstance() {
		return SingletonHolder.INSTANCE;
	}

	// 延迟加载单例
	private static class SingletonHolder {
		private static final AsHttpClient INSTANCE = new AsHttpClient();
	}

	// 私有构造函数，初始化客户端
	private AsHttpClient() {
		this(Boolean.parseBoolean(config.getStr("http.debug", "false")),
				config.getInt("http.maxRetries", DEFAULT_MAX_RETRIES));
	}

	/**
	 * 构造函数，允许自定义调试模式和重试次数。
	 *
	 * @param debug      是否启用调试模式
	 * @param maxRetries 最大重试次数
	 */
	private AsHttpClient(boolean debug, int maxRetries) {
		this.debug = debug;
		this.maxRetries = maxRetries;
//		this.httpClient = HttpClient.newBuilder().connectTimeout(DEFAULT_TIMEOUT).build();
		updateProxySelector(loadProxyConfig());
	}

	/**
	 * 加载配置文件（http.properties），支持多路径候选。
	 *
	 * @return 配置对象，失败时返回空配置
	 */
	private static Props loadConfig() {
		String[] candidatePaths = { DATA_DIR + "/config/http.properties", DATA_DIR + "/../config/http.properties" };

		for (String path : candidatePaths) {
			if (FileUtil.exist(path)) {
				logger.info("加载配置文件: {}", path);
				return PropsUtil.get(path);
			}
		}

		logger.warn("未找到配置文件: {}，使用默认配置", String.join(", ", candidatePaths));
		return new Props();
	}

	/**
	 * 加载初始代理配置，支持从配置文件或系统属性读取。
	 *
	 * @return 代理配置对象
	 */
	private ProxyConfig loadProxyConfig() {
		String proxyHost = config.getStr("http.proxyHost", System.getProperty("http.proxyHost"));
		String proxyPort = config.getStr("http.proxyPort", System.getProperty("http.proxyPort", "0"));
		String proxyUser = config.getStr("http.proxyUser", System.getProperty("http.proxyUser"));
		String proxyPassword = config.getStr("http.proxyPassword", System.getProperty("http.proxyPassword"));
		return new ProxyConfig(proxyHost, Integer.parseInt(proxyPort), proxyUser, proxyPassword);
	}

	/**
	 * 设置代理配置，支持用户名和密码。
	 *
	 * @param proxyHost     代理主机
	 * @param proxyPort     代理端口
	 * @param proxyUser     代理用户名（可选）
	 * @param proxyPassword 代理密码（可选）
	 */
	public synchronized void setProxy(String proxyHost, int proxyPort, String proxyUser, String proxyPassword) {
		ProxyConfig config = new ProxyConfig(proxyHost, proxyPort, proxyUser, proxyPassword);
		if (config.isValid()) {
			updateProxySelector(config);
			logger.info("代理已更新: {}:{}", proxyHost, proxyPort);
		} else {
			logger.warn("无效的代理配置，保持现有代理");
		}
	}

	/**
	 * 禁用代理。
	 */
	public synchronized void disableProxy() {
		updateProxySelector(new ProxyConfig(null, 0, null, null));
		logger.info("代理已禁用");
	}

	/**
	 * 更新代理选择器和认证器。
	 *
	 * @param proxyConfig 代理配置
	 */
	private void updateProxySelector(ProxyConfig proxyConfig) {
		ProxySelector proxySelector = createProxySelector(proxyConfig);
		Authenticator authenticator = createProxyAuthenticator(proxyConfig);
		// 同步更新 httpClient

		HttpClient.Builder builder = HttpClient.newBuilder().connectTimeout(DEFAULT_TIMEOUT);
		if (proxySelector != null) {
			builder.proxy(proxySelector);
			if (authenticator != null) {
				builder.authenticator(authenticator);
			}
		}

		this.httpClient = builder.build();
		logger.debug("代理选择器和认证器已更新，代理: {}, 认证: {}",
				proxySelector != null ? proxyConfig.proxyHost + ":" + proxyConfig.proxyPort : "无",
				authenticator != null ? "已配置" : "无");
	}

	/**
	 * 创建代理选择器。
	 *
	 * @param proxyConfig 代理配置
	 * @return 代理选择器，若无效则返回 null
	 */
	private ProxySelector createProxySelector(ProxyConfig proxyConfig) {
		if (!proxyConfig.isValid()) {
			return null;
		}

		final InetSocketAddress proxyAddr = new InetSocketAddress(proxyConfig.proxyHost, proxyConfig.proxyPort);
		logger.info("代理配置: {}:{}", proxyConfig.proxyHost, proxyConfig.proxyPort);

		return new ProxySelector() {
			@Override
			public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
				logger.warn("代理连接失败: {}，错误: {}", uri, ioe.getMessage());
			}

			@Override
			public java.util.List<Proxy> select(URI uri) {
				return java.util.List.of(new Proxy(Proxy.Type.HTTP, proxyAddr));
			}
		};
	}

	/**
	 * 创建代理认证器。
	 *
	 * @param proxyConfig 代理配置
	 * @return 认证器，若无认证信息则返回 null
	 */
	private Authenticator createProxyAuthenticator(ProxyConfig proxyConfig) {
		String proxyUser = proxyConfig.proxyUser;
		String proxyPassword = proxyConfig.proxyPassword;

		if (proxyUser != null && !proxyUser.isEmpty() && proxyPassword != null) {
			logger.info("代理认证配置，用户: {}", proxyUser);
			return new Authenticator() {
				@Override
				protected PasswordAuthentication getPasswordAuthentication() {
					if (getRequestingScheme().equalsIgnoreCase("http")
							|| getRequestingScheme().equalsIgnoreCase("https")) {
						return new PasswordAuthentication(proxyUser, proxyPassword.toCharArray());
					}
					return null;
				}
			};
		}
		return null;
	}

	/**
	 * 代理配置类，封装代理相关参数。
	 */
	private static class ProxyConfig {
		final String proxyHost;
		final int proxyPort;
		final String proxyUser;
		final String proxyPassword;

		ProxyConfig(String proxyHost, int proxyPort, String proxyUser, String proxyPassword) {
			this.proxyHost = proxyHost;
			this.proxyPort = proxyPort;
			this.proxyUser = proxyUser;
			this.proxyPassword = proxyPassword;
		}

		/**
		 * 检查代理配置是否有效。
		 *
		 * @return 是否有效
		 */
		boolean isValid() {
			return proxyHost != null && !proxyHost.isEmpty();
		}
	}

	/**
	 * 同步 GET 请求。
	 *
	 * @param url URL 地址
	 * @return 响应内容
	 * @throws HttpException 请求失败时抛出
	 */
	public String get(String url) throws HttpException {
		return get(url, null, DEFAULT_ENCODING);
	}

	/**
	 * 同步 GET 请求，支持自定义头和编码。
	 *
	 * @param url      URL 地址
	 * @param headers  请求头
	 * @param encoding 响应编码
	 * @return 响应内容
	 * @throws HttpException 请求失败时抛出
	 */
	public String get(String url, Map<String, String> headers, String encoding) throws HttpException {
		HttpRequest request = createRequestBuilder(url, headers).GET().build();
		return executeRequest(request, encoding);
	}

	/**
	 * 异步 GET 请求。
	 *
	 * @param url URL 地址
	 * @return 包含响应内容的 CompletableFuture
	 */
	public CompletableFuture<String> getAsync(String url) {
		return getAsync(url, null, DEFAULT_ENCODING);
	}

	/**
	 * 异步 GET 请求，支持自定义头和编码。
	 *
	 * @param url      URL 地址
	 * @param headers  请求头
	 * @param encoding 响应编码
	 * @return 包含响应内容的 CompletableFuture
	 */
	public CompletableFuture<String> getAsync(String url, Map<String, String> headers, String encoding) {
		HttpRequest request = createRequestBuilder(url, headers).GET().build();
		return executeRequestAsync(request, encoding);
	}

	/**
	 * 同步 POST 请求。
	 *
	 * @param url  URL 地址
	 * @param body 请求体
	 * @return 响应内容
	 * @throws HttpException 请求失败时抛出
	 */
	public String post(String url, String body) throws HttpException {
		return post(url, body, DEFAULT_ENCODING, "application/json");
	}

	/**
	 * 同步 POST 请求，支持自定义编码和内容类型。
	 *
	 * @param url         URL 地址
	 * @param body        请求体
	 * @param encoding    响应编码
	 * @param contentType 内容类型
	 * @return 响应内容
	 * @throws HttpException 请求失败时抛出
	 */
	public String post(String url, String body, String encoding, String contentType) throws HttpException {
		HttpRequest request = createRequestBuilder(url, null).header("Content-Type", contentType)
				.POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8)).build();
		return executeRequest(request, encoding);
	}

	/**
	 * 异步 POST 请求。
	 *
	 * @param url  URL 地址
	 * @param body 请求体
	 * @return 包含响应内容的 CompletableFuture
	 */
	public CompletableFuture<String> postAsync(String url, String body) {
		return postAsync(url, body, DEFAULT_ENCODING, "application/json");
	}

	/**
	 * 异步 POST 请求，支持自定义编码和内容类型。
	 *
	 * @param url         URL 地址
	 * @param body        请求体
	 * @param encoding    响应编码
	 * @param contentType 内容类型
	 * @return 包含响应内容的 CompletableFuture
	 */
	public CompletableFuture<String> postAsync(String url, String body, String encoding, String contentType) {
		HttpRequest request = createRequestBuilder(url, null).header("Content-Type", contentType)
				.POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8)).build();
		return executeRequestAsync(request, encoding);
	}

	/**
	 * 创建请求构建器，验证 URL 和头信息。
	 *
	 * @param url     URL 地址
	 * @param headers 请求头
	 * @return 请求构建器
	 * @throws HttpException URL 无效时抛出
	 */
	private HttpRequest.Builder createRequestBuilder(String url, Map<String, String> headers) {
		try {
			HttpRequest.Builder builder = HttpRequest.newBuilder().uri(URI.create(url)).timeout(DEFAULT_TIMEOUT);
			addHeaders(builder, headers != null ? headers : Map.of());
			return builder;
		} catch (IllegalArgumentException e) {
			throw new HttpException("无效的 URL: " + url, e);
		}
	}

	/**
	 * 执行同步请求，支持重试机制。
	 *
	 * @param request  请求对象
	 * @param encoding 响应编码
	 * @return 响应内容
	 * @throws HttpException 请求失败时抛出
	 */
	private String executeRequest(HttpRequest request, String encoding) throws HttpException {
		for (int attempt = 1; attempt <= maxRetries; attempt++) {
			try {
				HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
				int statusCode = response.statusCode();

				if (debug) {
					logger.debug("请求: {}, 状态: {}, 响应: {}", request.uri(), statusCode, response.body());
				}

				if (statusCode != 200) {
					throw new HttpException(getCause(statusCode) + " URL: " + request.uri());
				}

				return response.body();
			} catch (IOException e) {
				if (attempt == maxRetries) {
					throw new HttpException(
							"HTTP 请求失败，经过 " + maxRetries + " 次尝试: " + e.getMessage() + " URL: " + request.uri(), e);
				}
				logger.warn("请求尝试 {} 失败: {}，URL: {}，将在 {}ms 后重试...", attempt, e.getMessage(), request.uri(),
						100 * attempt);
				try {
					Thread.sleep(100 * attempt); // 指数退避
				} catch (InterruptedException ie) {
					Thread.currentThread().interrupt();
					throw new HttpException("请求被中断", ie);
				}
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
				throw new HttpException("请求被中断", e);
			}
		}
		throw new HttpException("请求执行过程中发生意外错误: " + request.uri());
	}

	/**
	 * 执行异步请求。
	 *
	 * @param request  请求对象
	 * @param encoding 响应编码
	 * @return 包含响应内容的 CompletableFuture
	 */
	private CompletableFuture<String> executeRequestAsync(HttpRequest request, String encoding) {
		return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()).thenApply(response -> {
			int statusCode = response.statusCode();

			if (debug) {
				logger.debug("请求: {}, 状态: {}, 响应: {}", request.uri(), statusCode, response.body());
			}

			if (statusCode != 200) {
				throw new RuntimeException(getCause(statusCode) + " URL: " + request.uri());
			}

			return response.body();
		}).exceptionally(throwable -> {
			throw new RuntimeException("异步 HTTP 请求失败: " + throwable.getMessage() + " URL: " + request.uri(), throwable);
		});
	}

	/**
	 * 添加请求头。
	 *
	 * @param builder 请求构建器
	 * @param headers 请求头
	 */
	private void addHeaders(HttpRequest.Builder builder, Map<String, String> headers) {
		headers.forEach(builder::header);
	}

	/**
	 * 编码请求参数。
	 *
	 * @param params 参数映射
	 * @return 编码后的参数字符串
	 */
	public static String encodeParameters(Map<String, Object> params) {
		if (params == null || params.isEmpty()) {
			return "";
		}
		return params.entrySet().stream().map(entry -> {
			try {
				return URLEncoder.encode(entry.getKey(), DEFAULT_ENCODING) + "="
						+ URLEncoder.encode(String.valueOf(entry.getValue()), DEFAULT_ENCODING);
			} catch (Exception e) {
				return "";
			}
		}).collect(Collectors.joining("&"));
	}

	/**
	 * 解析 JSON 响应。
	 *
	 * @param response 响应内容
	 * @param clazz    目标类
	 * @param <T>      泛型类型
	 * @return 解析后的对象
	 * @throws HttpException 解析失败时抛出
	 */
	public <T> T parseResponse(String response, Class<T> clazz) throws HttpException {
		try {
			return JSON.parseObject(response, clazz);
		} catch (Exception e) {
			logger.error("解析 JSON 响应失败: {}", response, e);
			throw new HttpException("解析 JSON 响应失败: " + e.getMessage(), e);
		}
	}

	/**
	 * 获取 HTTP 状态码对应的错误原因。
	 *
	 * @param statusCode 状态码
	 * @return 错误原因描述
	 */
	private static String getCause(int statusCode) {
		switch (statusCode) {
		case 304:
			return "未修改";
		case 400:
			return "错误请求: 请求无效";
		case 401:
			return "未授权: 缺少或错误的认证信息";
		case 403:
			return "禁止访问: 请求被拒绝";
		case 404:
			return "未找到: 请求的 URI 无效或资源不存在";
		case 500:
			return "服务器内部错误: 服务端发生错误";
		case 502:
			return "错误网关: 服务器宕机或正在升级";
		case 503:
			return "服务不可用: 服务器超载";
		default:
			return "未知错误";
		}
	}

	/**
	 * 测试主方法，展示基本用法。
	 *
	 * @param args 命令行参数
	 */
	public static void main(String[] args) {
		try {
			JSONObject json = new JSONObject();
			json.put("account", "123"); // 固定参数
			AsHttpClient client = AsHttpClient.getInstance();
			client.setProxy("127.0.0.1", 7890, null, null);
			String response = client.post("https://fdy.futuredao.io/api/v1/cow/px", json.toString());
			System.out.println("响应: " + response);
		} catch (HttpException e) {
			logger.error("请求失败", e);
		}
	}
}